package algo3c3g3.model;

import algo3c3g3.model.action.*;
import org.jdom.Element;

import algo3c3g3.model.persistence.Persistable;
import algo3c3g3.model.persistence.Persistence;

/**
 * Representa una unidad en juego.
 */
public class Unit implements Persistable
{   
    // Identificador unico de la unidad.
    private int unitID;
    
    // Indica si la unidad esta disponible en el juego.
    private boolean alive = true;
    
    // Posicion actual de la unidad.
    private Coord position;
    
    // Accion actual de la unidad.
    private Action currentAction;
    
    // Mapa al que esta asociada esta unidad.
    private WorldMap ownerMap;
    
    private World ownerWorld;
    
    // Direccion en la que la unidad se mueve horizontalmente.
    private int direction = 1;
    
    /**
     * @param id Identificador numerico unico de la unidad.
     * @param position Posicion en la que se crea la unidad.
     * @param action Accion inicial de la unidad.
     * @param worldMap Mapa al que esta unidad pertenece.
     */
    public Unit(int id, Coord position, Action action, WorldMap worldMap, World world)
    {
        unitID = id;
        this.position = new Coord(position);
        currentAction = action;
        ownerMap = worldMap;
        ownerWorld = world;
    }
    
    /**
     * La accion por defecto es Walker.
     * @param id Identificador numerico unico de la unidad.
     * @param position Posicion en la que se crea la unidad.
     * @param worldMap Mapa al que esta unidad pertenece.
     */
    public Unit(int id, Coord position, WorldMap worldMap, World world)
    {
        this(id, position, new Walker(), worldMap, world);
    }
    
    public Unit(Element element)
    {
        this.load(element);
    }

    public boolean isAlive()
    {
        return alive;
    }
    
    public void kill()
    {
        currentAction.dispose(this);
        alive = false;
        ownerWorld.notifyKilledUnit();
        
        //System.out.println("\nUnidad eliminada!!\n");
    }
    
    public void setSaved()
    {
        currentAction.dispose(this);
        alive = false;
    }
    
    public int getID()
    {
        return unitID;
    }
    
    public void setPosition(int x, int y)
    {
        position.setCoord(x, y);
    }
    
    public void setPosition(Coord c)
    {
        position = c;
    }
    
    public void setXPosition(int x)
    {
        position.setXCoord(x);
    }
    
    public void setYPosition(int y)
    {
        position.setYCoord(y);
    }
    
    public synchronized Coord getPosition()
    {
        return new Coord(position);
    }
    
    public synchronized int getXPosition()
    {
        return position.getXCoord();
    }
    
    public synchronized int getYPosition()
    {
        return position.getYCoord();
    }
    
    /**
     * Devuelve la proxima posicion hirizontal donde la unidad puede moverse
     * teniendo en cuenta su direccion.
     */
    public Coord getNextSidePosition()
    {
        return new Coord(position.getXCoord() + (1 * direction), position.getYCoord());
    }
    
    /**
     * Devuelve la posicion del bloque ubicado debajo
     * de la unidad.
     */
    public Coord getFloorPosition()
    {
        return new Coord(position.getXCoord(), position.getYCoord() + 1);
    }
    
    /**
     * Devuelve la posicion del bloque ubicado encima
     * de la unidad.
     */
    public Coord getTopPosition()
    {
        return new Coord(position.getXCoord(), position.getYCoord() - 1);
    }
    
    /**
     * Devuelve la referencia del mapa asignado a la unidad.
     */
    public WorldMap getOwnerMap()
    {
        return ownerMap;
    }
    
    /**
     * @param action Accion que se establece como accion actual
     * para esta unidad.
     */
    public synchronized void setAction(Action action)
    {
        currentAction.dispose(this);
        currentAction = action;
    }
    
    public Action getAction()
    {
        return currentAction;
    }
    
    /**
     * Ejecuta la accion actual de esta unidad sobre si misma si esta viva.
     */
    public void performAction()
    {
        if(alive)
            try
            {
                currentAction.perform(this);
            }
            catch(Exception e)
            {
                kill();
            }
    }
    
    /**
     * Devuelve la direccion en la que la unidad se mueve en forma horizontal.
     * @return 1 si se esta moviendo a la derecha y -1 si se
     * esta moviendo a la izquierda.
     */
    public int getDirection()
    {
        return direction;
    }
    
    /**
     * Invierte la direccion de la unidad.
     */
    public void changeDirection()
    {
        direction *= -1;
        
        //System.out.println("\nDireccion cambiada!!\n");
    }
    
    /**
     * Mueve a la unidad horizontalmente aumentando su posicion en x
     * en la direccion que tiene actualmente.
     */
    public void walk()
    {
        position.setXCoord(position.getXCoord() + (1 * direction));
    }
    
    /**
     * Mueve la unidad un bloque hacia abajo.
     */
    public void fall()
    {
        position.setYCoord(position.getYCoord() + 1);
    }
    
    /**
     * Mueve la unidad un bloque hacia arriba.
     */
    public void moveUp()
    {
        position.setYCoord(position.getYCoord() - 1);
    }
    
    /**
     * @return true si el bloque siguiente a la unidad teniendo en cuenta
     * su posicion es solido.
     */
    public boolean nextBlockIsSolid()
    {
        return ownerMap.blockAt(getNextSidePosition()).isSolid();
    }
    
    /**
     * @return true si el bloque debajo de la unidad es solido.
     */
    public boolean overSolidFloor()
    {
        return ownerMap.blockAt(getFloorPosition()).isSolid();
    }
    
    /**
     * @return true si el bloque encima de la unidad es solido.
     */
    public boolean topBlockIsSolid()
    {
        return ownerMap.blockAt(getTopPosition()).isSolid();
    }
    
    /**
     * @return true si el bloque ubicado en diagonal hacia arriba de la
     * unidad teniendo en cuenta su direccion es solido.
     */
    public boolean nextUpDiagBlockIsSolid()
    {
        return ownerMap.blockAt(position.getXCoord() + (1 * direction),
                                position.getYCoord() - 1).isSolid();
    }
    
    /**
     * @return true si el bloque debajo de la unidad es rompible.
     */
    public boolean floorIsBreakable()
    {
        return ownerMap.blockAt(getFloorPosition()).isBreakable();
    }
    
    private Block getNextBlockX()
    {
        return ownerMap.blockAt(getNextSidePosition());
    }
    
    private Block getNextBlockY()
    {
        return ownerMap.blockAt(getFloorPosition());
    }
    
    /**
     * Ejecuta sobre la unidad el efecto del material del bloque siguiente
     * teniendo en cuenta su direccion . Este efecto es determinado por el material.
     */
    public void materialEffectX()
    {
        getNextBlockX().affectUnitX(this);
    }
    
    /**
     * Ejecuta sobre la unidad el efecto del material del bloque
     * que esta debajo. Este efecto es determinado por el material.
     */
    public void materialEffectY()
    {
        getNextBlockY().affectUnitY(this);
    }
    
    public void printStatus()
    {
        if(!alive)
        {
            System.out.println("La unidad ya fue eliminada");
            return;
        }
        
        System.out.println("ID: " + unitID + ", Posicion: " + position);
        System.out.println("Direccion: " + ((direction == 1) ? "Derecha" : "Izquierda"));
        System.out.println("Accion: " + currentAction.getClass().getName() + "\n");
    }
    
    /**
     * es el encargado de serializar al unit.
     * @return el unit serializado dentro de un objeto Element
     */
    public Element save()
    {
        Element unit = new Element("Unit");
        
        unit.addContent(new Element("unitID").setText(Integer.toString(this.unitID)));
        unit.addContent(new Element("direction").setText(Integer.toString(this.getDirection())));
        unit.addContent(new Element("alive").setText(Boolean.toString(this.isAlive())));
        unit.addContent(getPosition().save());
        
        Element elementCurrentAction=new Element("currentAction");
        unit.addContent(elementCurrentAction);
        elementCurrentAction.setAttribute("className",
    		this.getAction().getClass().getCanonicalName());
        elementCurrentAction.addContent(this.getAction().save());

        return unit;
    }

    /**
     * es el encargado de cargar al unit.
     * @return void, crea el objeto Unit.
     */
    public void load(Element e)
    {
        this.unitID = Integer.parseInt(e.getChild("unitID").getText());
        this.direction = Integer.parseInt(e.getChild("direction").getText());
        this.alive = Boolean.valueOf(e.getChild("alive").getText());
        this.position = new Coord(e);

        Element elementCurrentAction = e.getChild("currentAction");

        try
        {
            this.currentAction = (Action) Class.forName(
                elementCurrentAction.getAttributeValue("className")).newInstance();
        }
        catch (InstantiationException e1)
        {
            e1.printStackTrace();
        }
        catch (IllegalAccessException e1)
        {
            e1.printStackTrace();
        }
        catch (ClassNotFoundException e1)
        {
            e1.printStackTrace();
        }

        this.currentAction.load((Element) elementCurrentAction.getChildren().iterator().next());
    }

    public void setWorldMap(WorldMap map)
    {
        this.ownerMap = map;
    }
    
    public void setWorld(World world)
    {
        ownerWorld = world;
    }
    
    public static void main(String[] args)
    {
    	WorldMap worldMap=new WorldMap(30,30);
    	Unit unit=new Unit(0,new Coord(0,0),new Blocker(),worldMap,new World(new Coord(0,0),new Coord(0,0),worldMap));
    	Persistence.toXMLFile(unit.save(),"C:\\salida.xml");
    	Unit unit2=new Unit(Persistence.getRootFromXMLFile("C:\\salida.xml"));
    	Persistence.toXMLFile(unit2.save(),"C:\\salida2.xml");  	
    }
}